package com.sh.data.engine.common.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public class LogArchiverUtil {

  // Method to archive the log file into a zip file
  public static void archiveLogFile(File logFile, ZipOutputStream zipOut) throws IOException {
    if (!logFile.exists() || !logFile.isFile()) {
      log.error("Log file not found: " + logFile.getPath());
      return;
    }

    ZipEntry zipEntry = new ZipEntry(logFile.getName());
    zipOut.putNextEntry(zipEntry);

    try (FileInputStream fileInputStream = new FileInputStream(logFile)) {
      byte[] buffer = new byte[1024];
      int length;
      while ((length = fileInputStream.read(buffer)) > 0) {
        zipOut.write(buffer, 0, length);
      }
    }

    log.info("Archived log file: " + logFile.getName());
  }

  // Method to get the previous day's date
  public static Date getPreviousDayDate() {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, -1); // Subtract one day
    return calendar.getTime();
  }

  // Method to archive all files modified on the previous day
  public static void archiveLogsByModifiedDate(
      String logDirectoryPath, String archiveDirectoryPath, Integer days) {
    Path logDir = Paths.get(logDirectoryPath);
    File logDirectory = new File(logDirectoryPath);
    if (!logDirectory.exists() || !logDirectory.isDirectory()) {
      log.error("Log directory not found: " + logDirectoryPath);
      return;
    }

    Date previousDay = getPreviousDayDate();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    // Ensure archive directory exists
    File archive = new File(archiveDirectoryPath);
    if (!archive.exists()) {
      archive.mkdirs();
    }
    String archiveFileName = "logs-" + sdf.format(previousDay) + ".zip";
    File archiveFile = new File(archiveDirectoryPath, archiveFileName);

    try (FileOutputStream fileOutputStream = new FileOutputStream(archiveFile);
        ZipOutputStream zipOut = new ZipOutputStream(fileOutputStream);
        Stream<Path> paths = Files.walk(logDir, 1)) {

      paths
          .filter(Files::isRegularFile)
          .filter(path -> path.getFileName().toString().endsWith(".log"))
          .filter(
              path -> {
                try {
                  return isFileModifiedWithinLastDays(path, days);
                } catch (IOException e) {
                  log.error("Error checking file modification date: " + path.toString());
                  return false;
                }
              })
          .forEach(
              path -> {
                try {
                  // Add the log file to the archive
                  archiveLogFile(path.toFile(), zipOut);

                  // Delete the log file after archiving
                  Files.delete(path);
                  ;
                } catch (IOException e) {
                  log.error(
                      "Error archiving/deleting file: " + path.toString() + " - " + e.getMessage());
                }
              });
    } catch (IOException e) {
      log.error("Error archiving log files: " + e.getMessage());
    }
  }

  // Method to check if a file was modified on the previous day
  public static boolean isFileModifiedOnPreviousDay(Path file, Date previousDay)
      throws IOException {
    FileTime fileTime = Files.getLastModifiedTime(file);
    Calendar fileModifiedCalendar = Calendar.getInstance();
    fileModifiedCalendar.setTimeInMillis(fileTime.toMillis());
    Calendar previousDayCalendar = Calendar.getInstance();
    previousDayCalendar.setTime(previousDay);
    previousDayCalendar.set(Calendar.HOUR_OF_DAY, 0);
    previousDayCalendar.set(Calendar.MINUTE, 0);
    previousDayCalendar.set(Calendar.SECOND, 0);
    previousDayCalendar.set(Calendar.MILLISECOND, 0);

    // Check if the file was modified on the previous day or earlier
    return fileModifiedCalendar.get(Calendar.YEAR) <= previousDayCalendar.get(Calendar.YEAR)
        && fileModifiedCalendar.get(Calendar.DAY_OF_YEAR)
            <= previousDayCalendar.get(Calendar.DAY_OF_YEAR);
  }

  public static boolean isFileModifiedWithinLastDays(Path file, Integer day) throws IOException {
    FileTime fileTime = Files.getLastModifiedTime(file);
    Calendar fileModifiedCalendar = Calendar.getInstance();
    fileModifiedCalendar.setTimeInMillis(fileTime.toMillis());

    // Calculate the date for 7 days ago
    Calendar sevenDaysAgoCalendar = Calendar.getInstance();
    sevenDaysAgoCalendar.add(Calendar.DAY_OF_YEAR, day);

    // Check if the file was modified within the last 7 days
    return fileModifiedCalendar.before(sevenDaysAgoCalendar);
  }

  public static void archiveLogsByStrategy(
      String logDirectoryPath, Integer days, long fileCountLimit) {
    String archiveDirectoryPath = logDirectoryPath + "archive";
    // Archive logs by modified date (previous day)
    archiveLogsByModifiedDate(logDirectoryPath, archiveDirectoryPath, days);
    // Archive logs if the number of log files exceeds the fileCountLimit
    try {
      long logFileCount = countLogFilesInDirectory(logDirectoryPath);
      if (logFileCount > fileCountLimit) {
        log.info("Log file count exceeds the limit of " + fileCountLimit + ". Archiving logs.");
        archiveLogsExceedingFileCount(logDirectoryPath, archiveDirectoryPath, fileCountLimit);
      }
    } catch (IOException e) {
      log.error("Error counting log files: " + e.getMessage());
    }
  }

  public static long countLogFilesInDirectory(String logDirectoryPath) throws IOException {
    try (Stream<Path> logFiles = Files.walk(Paths.get(logDirectoryPath), 1)) {
      return logFiles
          .filter(Files::isRegularFile)
          .filter(path -> path.getFileName().toString().endsWith(".log"))
          .count();
    }
  }

  // Method to archive files when log file count exceeds a limit
  // Method to archive files when log file count exceeds a limit while keeping the latest
  // logCountLimit files
  public static void archiveLogsExceedingFileCount(
      String logDirectoryPath, String archiveDirectoryPath, long fileCountLimit) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date currentDate = new Date();
    String archiveFileName = "logs-exceeding-limit-" + sdf.format(currentDate) + ".zip";
    File archiveFile = new File(archiveDirectoryPath, archiveFileName);
    // Ensure the archive directory exists
    File archiveDir = new File(archiveDirectoryPath);
    if (!archiveDir.exists()) {
      archiveDir.mkdirs();
    }
    try (Stream<Path> paths = Files.walk(Paths.get(logDirectoryPath), 1)) {
      List<Path> logFiles =
          paths
              .filter(Files::isRegularFile)
              .filter(path -> path.getFileName().toString().endsWith(".log"))
              .sorted(
                  Comparator.comparing(
                      path -> {
                        try {
                          return Files.getLastModifiedTime(path);
                        } catch (IOException e) {
                          log.error("Error getting last modified time for: " + path);
                          return FileTime.fromMillis(0); // Default value for sorting
                        }
                      }))
              .collect(Collectors.toList());
      // Check if the count exceeds the limit
      if (logFiles.size() > fileCountLimit) {
        List<Path> filesToArchive =
            logFiles.subList(0, logFiles.size() - (int) fileCountLimit); // Get the excess files

        try (FileOutputStream fileOutputStream = new FileOutputStream(archiveFile);
            ZipOutputStream zipOut = new ZipOutputStream(fileOutputStream)) {
          // Archive the excess files
          for (Path path : filesToArchive) {
            archiveLogFile(path.toFile(), zipOut);
            // Delete the log file after archiving
            Files.delete(path);
          }
        } catch (IOException e) {
          log.error("Error archiving logs based on file count: " + e.getMessage());
        }
      }
    } catch (IOException e) {
      log.error("Error processing log files: " + e.getMessage());
    }
  }

  public static void main(String[] args) {
    String logDirectoryPath = "/data/data_engine_file_storage/log/import/"; // Path to log files
    String archiveDirectoryPath =
        "/data/data_engine_file_storage/log/import/archive"; // Path to store archived zip files
    // Archive logs modified on the previous day
    archiveLogsByStrategy(logDirectoryPath, -7, 30);
  }
}
